Skip to content

feat(env): add environment validation with tool detection#306

Merged
frankbria merged 5 commits into
mainfrom
feature/environment-validation
Jan 29, 2026
Merged

feat(env): add environment validation with tool detection#306
frankbria merged 5 commits into
mainfrom
feature/environment-validation

Conversation

@frankbria

@frankbria frankbria commented Jan 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add comprehensive environment validation system with tool detection
  • Implement CLI commands for environment health checking and tool installation
  • Support for Python, JavaScript, Rust, and generic tool ecosystems

Changes

New Core Modules

codeframe/core/environment.py - Tool detection and validation engine:

  • ToolDetector base class with shutil.which() detection and version parsing
  • Ecosystem-specific detectors: PythonToolDetector, JavaScriptToolDetector, RustToolDetector, GenericToolDetector
  • ProjectTypeDetector - auto-detects project type from manifest files (pyproject.toml, package.json, Cargo.toml, go.mod)
  • EnvironmentValidator - validates environment with health scoring and recommendations
  • Version parsing supporting formats: "1.2.3", "v1.2.3", "pytest 7.4.0", "1.2.3-rc1"

codeframe/core/installer.py - Platform-specific tool installation:

  • ToolInstaller main class delegating to sub-installers
  • PipInstaller - Python packages (prefers uv over pip)
  • NpmInstaller - JavaScript packages (global or local)
  • CargoInstaller - Rust tools (rustup components and cargo packages)
  • SystemInstaller - System packages (apt/brew/choco based on platform)
  • Installation history tracking in .codeframe/environment.json

New CLI Commands

codeframe/cli/env_commands.py:

  • cf env check - Quick environment validation with health score
  • cf env doctor - Comprehensive diagnostics with Rich tables
  • cf env install-missing <tool> - Install specific missing tool
  • cf env auto-install - Install all missing required tools

Test plan

  • 47 tests for environment module (tests/core/test_environment.py)
  • 35 tests for installer module (tests/core/test_installer.py)
  • 12 tests for CLI commands (tests/cli/test_env_commands.py)
  • All 94 new tests passing
  • All 807 existing core tests still passing
  • Manual CLI verification:
    • cf env check shows 100% health score
    • cf env doctor displays tool table with versions

Summary by CodeRabbit

  • New Features

    • Added env CLI command group for environment validation and tool management
    • env check: validates project environment and displays health status
    • env doctor: provides comprehensive diagnostics with tool detection and recommendations
    • env install-missing: installs specified missing tools interactively
    • env auto-install: automatically installs all missing tools with progress tracking
    • Supports Python, JavaScript, and Rust project detection with cross-platform installation
  • Documentation

    • Added environment management documentation to CLI reference
  • Chores

    • Updated GitHub workflow configurations

✏️ Tip: You can customize this high-level summary in your review settings.

Implements enhanced environment validation with tool detection:

- Add codeframe/core/environment.py:
  - ToolDetector base class with version parsing
  - Ecosystem-specific detectors (Python, JavaScript, Rust, Generic)
  - ProjectTypeDetector for auto-detecting project type from manifests
  - EnvironmentValidator with health scoring and recommendations
  - Version parsing and compatibility checking

- Add codeframe/core/installer.py:
  - ToolInstaller main class delegating to sub-installers
  - PipInstaller for Python packages (prefers uv over pip)
  - NpmInstaller for JavaScript packages
  - CargoInstaller for Rust tools (rustup components and cargo packages)
  - SystemInstaller for system packages (apt/brew/choco)
  - Installation history tracking in .codeframe/environment.json

- Add codeframe/cli/env_commands.py:
  - `cf env check` - Quick environment validation with health score
  - `cf env doctor` - Comprehensive diagnostics with Rich tables
  - `cf env install-missing <tool>` - Install specific tool
  - `cf env auto-install` - Install all missing required tools

Test coverage: 94 new tests (all passing)
- tests/core/test_environment.py (47 tests)
- tests/core/test_installer.py (35 tests)
- tests/cli/test_env_commands.py (12 tests)
@coderabbitai

coderabbitai Bot commented Jan 29, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Introduces a comprehensive environment validation and tool installation system to the CodeFRAME CLI, including commands for checking project health, installing missing tools, comprehensive diagnostics, and batch tool management. The system detects project types, manages tool versions, and provides actionable recommendations.

Changes

Cohort / File(s) Summary
CLI Integration
codeframe/cli/__init__.py, codeframe/cli/app.py
Registers env_app as a Typer sub-application; duplication noted with env_app added in two locations within each file.
Environment Validation Core
codeframe/core/environment.py
Introduces tool detection and version management system: ToolStatus enum, ToolInfo/ValidationResult dataclasses, ToolDetector base class with ecosystem-specific implementations (Python, JavaScript, Rust, Generic), ProjectTypeDetector for manifest-based type inference, and EnvironmentValidator orchestrating the complete validation workflow with health scoring and recommendations.
Tool Installation Framework
codeframe/core/installer.py
Implements platform-aware installation system with InstallStatus enum and InstallResult dataclass; includes concrete installers (PipInstaller, NpmInstaller, CargoInstaller, SystemInstaller) delegated by ToolInstaller; supports history tracking, verification, and platform detection.
Environment CLI Commands
codeframe/cli/env_commands.py
Adds four new CLI commands: check (quick validation), doctor (comprehensive diagnostics), install-missing (individual tool install), and auto-install (batch tool installation); includes progress UI and error handling.
CLI Command Tests
tests/cli/test_env_commands.py
Comprehensive unit tests for environment CLI commands covering healthy/unhealthy states, tool installation, confirmation prompts, edge cases, and exit code validation.
Environment Core Tests
tests/core/test_environment.py
Extensive test coverage for tool detection, version parsing/comparison, project type detection, and environment validation workflows; includes ecosystem-specific detector tests and integration scenarios.
Installer Tests
tests/core/test_installer.py
Thorough test suite for tool installation framework validating platform detection, installer delegation, command generation, installation history management, and end-to-end installation flows.
CI/CD Workflows
.github/workflows/claude-code-review.yml, .github/workflows/opencode-review.yml
Enables claude-code-review job by removing if: false guard; disables opencode-review job by adding if: false condition.
Documentation
docs/CLI_WIREFRAME.md
Documents new environment management commands and their expected outputs in the CLI wireframe.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI as env check<br/>(Command)
    participant Validator as EnvironmentValidator
    participant Detector as ProjectTypeDetector
    participant ToolDetect as ToolDetector<br/>variants
    participant Output as Output<br/>(UI)

    User->>CLI: Run 'env check --project .'
    CLI->>Validator: validate_environment(project_path)
    Validator->>Detector: detect_project_type(project_dir)
    Detector-->>Validator: project_type
    Validator->>ToolDetect: _detect_all_tools(required_tools)
    ToolDetect-->>Validator: detected_tools dict
    Validator->>Validator: calculate_health_score()
    Validator->>Validator: generate_recommendations()
    Validator-->>CLI: ValidationResult
    CLI->>Output: Display health score,<br/>tools, recommendations
    Output-->>User: Exit 0 (healthy)<br/>or 1 (unhealthy)
Loading
sequenceDiagram
    actor User
    participant CLI as env install-missing<br/>(Command)
    participant Installer as ToolInstaller
    participant SubInstaller as Concrete Installer<br/>(Pip/Npm/etc)
    participant Process as subprocess
    participant History as history.json

    User->>CLI: Run 'env install-missing pylint'
    CLI->>Installer: install_tool(tool_name, confirm=True)
    Installer->>Installer: _get_installer_for_tool()
    Installer->>SubInstaller: can_install(tool_name)
    SubInstaller-->>Installer: true/false
    Installer->>SubInstaller: get_install_command()
    SubInstaller-->>Installer: command_string
    CLI->>CLI: Display command,<br/>prompt for confirmation
    User->>CLI: Confirm installation
    Installer->>SubInstaller: install_tool(confirm=False)
    SubInstaller->>Process: subprocess.run(command)
    Process-->>SubInstaller: result
    SubInstaller->>Installer: InstallResult
    Installer->>History: record_installation(result)
    Installer-->>CLI: InstallResult
    CLI->>User: Display success/failure
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A hop, skip, and tool detection away!
New validators dance through project arrays,
With installers singing in harmony sweet,
Our environment commands make dev setups neat.
Bunny approves this ecosystem feast!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding environment validation with tool detection capabilities.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/environment-validation

Comment @coderabbitai help to get the list of available commands and usage tips.

@macroscopeapp

macroscopeapp Bot commented Jan 29, 2026

Copy link
Copy Markdown
Contributor

Add environment validation with tool detection and expose env CLI commands in environment.py and env_commands.py

Introduce environment validators and tool detectors with version parsing/comparison, add installers for Python/JS/Rust/system tools, and wire new env CLI commands for check, doctor, and installs; include tests across core and CLI and update workflows to enable code review.

📍Where to Start

Start with EnvironmentValidator.validate_environment in environment.py, then review CLI entrypoints in env_app within env_commands.py.


Macroscope summarized a6b7867.

def install_tool(
self,
tool_name: str,
confirm: bool = True,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low

The confirm parameter is declared but never used—installation proceeds unconditionally. Consider either implementing confirmation logic or removing the parameter to avoid misleading callers.

🚀 Want me to fix this? Reply ex: "fix it for me".

Comment thread codeframe/core/installer.py
return installer.get_install_command(tool_name)
return ""

def verify_installation(self, tool_name: str) -> bool:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low

Using shutil.which(tool_name) assumes the CLI name equals the package name; many tools differ and Python-only libs have no CLI. Suggest a centralized resolver that maps tool→executable and uses importlib.util.find_spec() for libs, then use it in verify_installation and installers’ skip checks.

🚀 Want me to fix this? Reply ex: "fix it for me".

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In `@codeframe/core/environment.py`:
- Around line 93-101: The is_healthy_with_threshold method currently only checks
health_score against the threshold while is_healthy also requires no missing
tools; update is_healthy_with_threshold (in class containing health_score and
missing_tools) to mirror is_healthy semantics by returning self.health_score >=
threshold and len(self.missing_tools) == 0 so callers get consistent behavior
with the missing_tools check.
- Around line 158-168: In compare_versions, do not return 0 when
parse_version(version1) or parse_version(version2) is None because that silently
treats unparseable versions as equal; instead change the behavior in
compare_versions to return -1 (or raise a clear ValueError) when either v1 or v2
is None so that check_version_compatibility will fail the >= min_version check;
update the compare_versions function (and any docstring/comments) to document
this defensive behavior and ensure callers handle the error if you choose to
raise.

In `@codeframe/core/installer.py`:
- Around line 121-126: The install_tool method's confirm parameter is documented
but ignored; update install_tool (and the same method in NpmInstaller,
CargoInstaller, SystemInstaller) to actually prompt when confirm is True and
abort when the user declines (unless force is True). Locate the install_tool
implementations and add a short confirmation step (e.g., prompt the user with a
y/n question) that returns an InstallResult indicating cancellation if the user
answers no, ensure that when force is True the prompt is skipped, and keep the
method's existing return semantics and error handling.
- Around line 137-143: The current pre-install check uses
shutil.which(tool_name), which only detects CLI executables and thus misses
importable Python libraries (e.g., httpx, requests); update the logic in the
installer (around the block that references tool_name, force, and returns
InstallResult/InstallStatus) to distinguish CLI tools from Python packages and,
for Python libraries, check importability via importlib (e.g.,
importlib.util.find_spec(package_name) or try
importlib.import_module(package_name)) instead of shutil.which; add or use a
parameter/flag (e.g., package_name or is_python_package/tool_type) so callers
can indicate a library vs executable and ensure the skipped/installed decision
uses the correct check.

In `@tests/cli/test_env_commands.py`:
- Around line 10-13: Remove the unused imports causing F401 in
tests/cli/test_env_commands.py: drop Path from pathlib, and MagicMock and patch
from unittest.mock if they are not referenced in this file; keep only the
imports actually used (e.g., pytest) so unused symbols (Path, MagicMock, patch)
are removed from the import block.

In `@tests/core/test_environment.py`:
- Around line 11-17: Remove the unused imports causing F401 in
tests/core/test_environment.py by editing the top import block to only import
symbols actually referenced in the file; inspect usage of shutil, subprocess,
Path, Optional, MagicMock, patch, call, and pytest, then delete any of those
names that are not used (or collapse them into a single minimal import line).
Ensure imports like MagicMock/patch/call remain only if the test uses
unittest.mock functionality and keep pytest only if used; re-run linting to
confirm F401 is resolved.

In `@tests/core/test_installer.py`:
- Around line 10-15: The file's import block contains unused imports causing
F401 lint failures; remove the unused symbols (e.g., json, subprocess, Path from
pathlib, and unused names from unittest.mock such as MagicMock, patch, call, and
pytest if not referenced elsewhere) from the top-level imports in
tests/core/test_installer.py so only actually used imports remain (locate the
import statement that declares json, subprocess, Path, MagicMock, patch, call,
pytest and delete the unused ones).
🧹 Nitpick comments (4)
codeframe/core/installer.py (3)

38-51: Consider logging when defaulting to linux for unknown platforms.

When platform.system() returns an unexpected value (e.g., "FreeBSD"), silently defaulting to "linux" may cause confusing failures. A debug log would aid troubleshooting.

🔧 Suggested improvement
     system = platform.system()
     if system == "Linux":
         return "linux"
     elif system == "Darwin":
         return "darwin"
     elif system == "Windows":
         return "windows"
+    logger.debug(f"Unknown platform '{system}', defaulting to linux")
     return "linux"  # Default to linux

265-272: Missing TimeoutExpired handling unlike PipInstaller.

PipInstaller explicitly catches subprocess.TimeoutExpired (lines 172-178), but NpmInstaller catches only generic Exception. This loses timeout-specific context in error messages. The same inconsistency exists in CargoInstaller.

♻️ Add explicit timeout handling for consistency
+        except subprocess.TimeoutExpired:
+            return InstallResult(
+                tool_name=tool_name,
+                status=InstallStatus.FAILED,
+                message=f"Installation of {tool_name} timed out",
+                command_used=command,
+            )
         except Exception as e:
             return InstallResult(

537-543: Silent JSONDecodeError handling loses diagnostic context.

When the history file is corrupted, silently falling back to an empty dict makes the corruption invisible to debugging. Consider logging a warning.

🔧 Add warning log for corrupted history file
         if self.history_file.exists():
             try:
                 with open(self.history_file) as f:
                     history = json.load(f)
             except json.JSONDecodeError:
-                pass
+                logger.warning(f"Corrupted history file at {self.history_file}, starting fresh")
codeframe/core/environment.py (1)

638-652: _detect_conflicts is a stub with no implementation.

The method always returns an empty list. The comment mentions it "could be expanded," but the PR summary advertises conflict detection as a feature. Consider either implementing basic conflict detection (e.g., multiple Python versions, pip vs uv, npm vs yarn) or updating the PR description to note this is a future enhancement.

Would you like me to help implement basic conflict detection logic, such as detecting multiple package managers for the same ecosystem?

Comment on lines +93 to +101
@property
def is_healthy(self) -> bool:
"""Check if environment is healthy (score >= 0.8)."""
return self.health_score >= 0.8 and len(self.missing_tools) == 0

def is_healthy_with_threshold(self, threshold: float) -> bool:
"""Check health against custom threshold."""
return self.health_score >= threshold

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

is_healthy_with_threshold ignores missing_tools unlike is_healthy.

is_healthy (line 96) checks both health_score >= 0.8 and len(self.missing_tools) == 0, but is_healthy_with_threshold only checks the score. This inconsistency may cause unexpected behavior when callers assume the custom threshold method has the same semantics.

🐛 Suggested fix for consistency
     def is_healthy_with_threshold(self, threshold: float) -> bool:
         """Check health against custom threshold."""
-        return self.health_score >= threshold
+        return self.health_score >= threshold and len(self.missing_tools) == 0
🤖 Prompt for AI Agents
In `@codeframe/core/environment.py` around lines 93 - 101, The
is_healthy_with_threshold method currently only checks health_score against the
threshold while is_healthy also requires no missing tools; update
is_healthy_with_threshold (in class containing health_score and missing_tools)
to mirror is_healthy semantics by returning self.health_score >= threshold and
len(self.missing_tools) == 0 so callers get consistent behavior with the
missing_tools check.

Comment on lines +158 to +168
v1 = parse_version(version1)
v2 = parse_version(version2)

if v1 is None or v2 is None:
return 0 # Can't compare, assume equal

if v1 < v2:
return -1
elif v1 > v2:
return 1
return 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Returning 0 when version parsing fails may silently pass incompatible versions.

If one version string is unparseable (e.g., malformed output from a tool), compare_versions returns 0, treating them as equal. In check_version_compatibility, this means an unparseable version passes the >= min_version check. Consider returning a value that fails the compatibility check instead, or raising an exception.

🛡️ Suggested defensive approach
     if v1 is None or v2 is None:
-        return 0  # Can't compare, assume equal
+        # Can't compare - return -1 to fail version checks defensively
+        logger.debug(f"Version comparison failed: {version1!r} vs {version2!r}")
+        return -1
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
v1 = parse_version(version1)
v2 = parse_version(version2)
if v1 is None or v2 is None:
return 0 # Can't compare, assume equal
if v1 < v2:
return -1
elif v1 > v2:
return 1
return 0
if v1 is None or v2 is None:
# Can't compare - return -1 to fail version checks defensively
logger.debug(f"Version comparison failed: {version1!r} vs {version2!r}")
return -1
🤖 Prompt for AI Agents
In `@codeframe/core/environment.py` around lines 158 - 168, In compare_versions,
do not return 0 when parse_version(version1) or parse_version(version2) is None
because that silently treats unparseable versions as equal; instead change the
behavior in compare_versions to return -1 (or raise a clear ValueError) when
either v1 or v2 is None so that check_version_compatibility will fail the >=
min_version check; update the compare_versions function (and any
docstring/comments) to document this defensive behavior and ensure callers
handle the error if you choose to raise.

Comment on lines +121 to +126
def install_tool(
self,
tool_name: str,
confirm: bool = True,
force: bool = False,
) -> InstallResult:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

The confirm parameter is documented but not implemented.

The docstring states "Whether to prompt for confirmation" but the parameter is never used. This pattern repeats across all installers (NpmInstaller, CargoInstaller, SystemInstaller). Either implement the confirmation logic or remove the parameter to avoid misleading callers.

🤖 Prompt for AI Agents
In `@codeframe/core/installer.py` around lines 121 - 126, The install_tool
method's confirm parameter is documented but ignored; update install_tool (and
the same method in NpmInstaller, CargoInstaller, SystemInstaller) to actually
prompt when confirm is True and abort when the user declines (unless force is
True). Locate the install_tool implementations and add a short confirmation step
(e.g., prompt the user with a y/n question) that returns an InstallResult
indicating cancellation if the user answers no, ensure that when force is True
the prompt is skipped, and keep the method's existing return semantics and error
handling.

Comment on lines +137 to +143
# Check if already installed
if not force and shutil.which(tool_name):
return InstallResult(
tool_name=tool_name,
status=InstallStatus.SKIPPED,
message=f"{tool_name} is already installed",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

shutil.which() won't detect installed Python libraries.

Packages like httpx and requests are importable libraries, not CLI executables. The check on line 138 will always return None for these, causing unnecessary reinstallation attempts.

🐛 Suggested fix: use importlib or distinguish libraries from CLI tools
+import importlib.util
+
+# In PipInstaller class, add method:
+def _is_installed(self, tool_name: str) -> bool:
+    """Check if a Python package is installed."""
+    # First check if it's a CLI tool
+    if shutil.which(tool_name):
+        return True
+    # Then check if it's an importable package
+    spec = importlib.util.find_spec(tool_name.replace("-", "_"))
+    return spec is not None

 def install_tool(self, tool_name: str, confirm: bool = True, force: bool = False) -> InstallResult:
     # Check if already installed
-    if not force and shutil.which(tool_name):
+    if not force and self._is_installed(tool_name):
🤖 Prompt for AI Agents
In `@codeframe/core/installer.py` around lines 137 - 143, The current pre-install
check uses shutil.which(tool_name), which only detects CLI executables and thus
misses importable Python libraries (e.g., httpx, requests); update the logic in
the installer (around the block that references tool_name, force, and returns
InstallResult/InstallStatus) to distinguish CLI tools from Python packages and,
for Python libraries, check importability via importlib (e.g.,
importlib.util.find_spec(package_name) or try
importlib.import_module(package_name)) instead of shutil.which; add or use a
parameter/flag (e.g., package_name or is_python_package/tool_type) so callers
can indicate a library vs executable and ensure the skipped/installed decision
uses the correct check.

Comment thread tests/cli/test_env_commands.py Outdated
Comment on lines +10 to +13
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove unused imports to fix F401 lint failures

CI flags unused imports in this block; please drop them to unblock the test suite.

🧹 Proposed fix
-from pathlib import Path
-from unittest.mock import MagicMock, patch
-
-import pytest
+from unittest.mock import patch
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from unittest.mock import patch
🧰 Tools
🪛 GitHub Actions: Test Suite (Unit + E2E)

[error] 10-10: F401: Remove unused import: pathlib.Path


[error] 11-11: F401: Remove unused import: unittest.mock.MagicMock


[error] 13-13: F401: Remove unused import: pytest

🤖 Prompt for AI Agents
In `@tests/cli/test_env_commands.py` around lines 10 - 13, Remove the unused
imports causing F401 in tests/cli/test_env_commands.py: drop Path from pathlib,
and MagicMock and patch from unittest.mock if they are not referenced in this
file; keep only the imports actually used (e.g., pytest) so unused symbols
(Path, MagicMock, patch) are removed from the import block.

Comment thread tests/core/test_environment.py Outdated
Comment thread tests/core/test_installer.py Outdated
@frankbria frankbria linked an issue Jan 29, 2026 that may be closed by this pull request
28 tasks
Repository owner deleted a comment from github-actions Bot Jan 29, 2026
@frankbria

Copy link
Copy Markdown
Owner Author

Code Review Summary

Overall, this is a well-implemented feature that addresses a critical gap in the Golden Path. The code follows established patterns and includes comprehensive test coverage.

✅ Strengths

  • Excellent test coverage: 94 new tests across 3 test files
  • Good architecture: Clean separation with sub-installer pattern, appropriate abstraction
  • Well-documented: Comprehensive docstrings and type hints throughout
  • Golden Path alignment: Addresses the critical "environment validation" gap
  • CLI-first design: Works without requiring a FastAPI server

⚠️ Recommendations

1. CLI Wireframe Alignment

The env subcommand group (check, doctor, install-missing, auto-install) is not specified in CLI_WIREFRAME.md. While this feature fills a documented gap, consider updating the wireframe to document it.

2. Command Splitting in Installer

In installer.py, several places use .split() to build subprocess arguments. While currently safe (commands come from hardcoded strings), this pattern could fail if tool names contain spaces.

Recommendation: Consider using shlex.split() for safer parsing, or constructing the list directly.

3. Version Parsing Edge Cases

The parse_version function uses a simple regex pattern which may fail for tools with non-standard version formats (e.g., 1.2.3+build.456, complex pre-release versions).

Recommendation: Consider documenting the supported version formats more explicitly.

4. Platform Detection Default

In installer.py, get_platform() defaults to "linux" for unknown platforms. Consider logging a warning when the default is used.

Performance Notes

  • Subprocess timeouts are appropriate (10s for version detection, 300s/600s for installations)
  • No obvious performance bottlenecks

Test Coverage

  • Excellent coverage of core functionality
  • Tests properly mock subprocess and file system operations

Status: ✅ Approve with minor suggestions

Repository owner deleted a comment from github-actions Bot Jan 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@tests/test_new_feature.py`:
- Around line 3-12: The test function test_new_feature is a placeholder that
only asserts True==True; either implement it with real assertions validating the
new environment validation feature (e.g., call the public API/function(s) added
in the PR and assert expected outputs/errors) or remove the function if it's no
longer needed; locate the test_new_feature function in the tests to replace the
placeholder Act/Assert with calls to the new feature's functions/classes and
proper assertions (or delete the test) so it provides actual coverage.
🧹 Nitpick comments (2)
tests/core/test_installer.py (1)

31-47: Consider adding a test for unknown platform default.

The tests cover Linux, macOS, and Windows, but per the PR comments, get_platform() defaults to "linux" for unknown platforms. Consider adding a test to verify this behavior:

def test_get_platform_unknown_defaults_to_linux(self):
    """Test that unknown platform defaults to linux."""
    with patch("platform.system", return_value="FreeBSD"):
        assert get_platform() == "linux"
tests/cli/test_env_commands.py (1)

1-23: Consider adding pytestmark = pytest.mark.v2 for CLI tests.

Per coding guidelines, CLI tests should be marked with v2. While the guideline specifically mentions "CLI tests using codeframe.cli.app", these tests exercise env_app which is a sub-application of the main CLI. Consider adding the module-level marker:

 """Tests for environment CLI commands.
 ...
 """
 
+import pytest
 from unittest.mock import patch
 
 from typer.testing import CliRunner
 ...
+
+pytestmark = pytest.mark.v2

As per coding guidelines: "Mark new v2 tests with pytest.mark.v2 decorator or module-level pytestmark = pytest.mark.v2 for CLI tests using codeframe.cli.app"

Comment thread tests/test_new_feature.py Outdated
1. CLI Wireframe Alignment
   - Add env subcommand documentation to CLI_WIREFRAME.md
   - Document check, doctor, install-missing, auto-install commands

2. Safer Command Splitting
   - Replace .split() with explicit list construction in installers
   - Add _get_install_cmd_parts() methods for safe subprocess args

3. Document Version Parsing Limitations
   - Add explicit supported/unsupported version format documentation
   - Note limitations for build metadata and pre-release ordering

4. Platform Detection Warning
   - Log warning when defaulting to 'linux' for unknown platforms

5. Test Fixes
   - Update integration tests to properly mock shutil.which for uv checks
Comment on lines +585 to +587
pass

# Add new entry

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium

If the history file contains valid JSON without an "installations" key, line 588 raises a KeyError. Consider using history.setdefault("installations", {}) before accessing it.

Suggested change
pass
# Add new entry
pass
history.setdefault("installations", {})

🚀 Want me to fix this? Reply ex: "fix it for me".

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@codeframe/core/installer.py`:
- Around line 349-364: CargoInstaller.install_tool currently proceeds without
verifying whether the requested tool is already installed, causing inconsistent
behavior with NpmInstaller; update CargoInstaller.install_tool to perform the
same pre-install check as NpmInstaller by calling the existing installer check
method (e.g., self.is_installed(tool_name) or the class's installed-tools
lookup) and: if the tool is present and force is False, return the appropriate
InstallResult (skipped/already_installed) without running install steps; if
confirm is required keep the existing confirmation flow; only continue to run
the install routine when the tool is not installed or force is True.
- Around line 250-267: NpmInstaller.install_tool currently always runs the
installer even when force is False; add a pre-install existence check similar to
PipInstaller by using shutil.which() (or equivalent) to detect if the tool
binary is already available when global_install is True and force is False, and
if found return an InstallResult indicating skipped/already-installed instead of
performing npm install; update NpmInstaller.install_tool to check
shutil.which(tool_name) (or the resolved binary name) and respect the force and
global_install flags before invoking the install logic.
🧹 Nitpick comments (8)
codeframe/core/installer.py (3)

193-200: Consider catching more specific exceptions.

The bare except Exception is overly broad and may mask unexpected errors. Consider catching specific exceptions like FileNotFoundError or PermissionError separately, or at minimum re-raise KeyboardInterrupt and SystemExit.

♻️ Proposed refinement
-        except Exception as e:
+        except (FileNotFoundError, PermissionError, OSError) as e:
             return InstallResult(
                 tool_name=tool_name,
                 status=InstallStatus.FAILED,
                 message=f"Installation error: {e}",
                 command_used=command,
                 error_output=str(e),
             )

564-568: Recording SKIPPED results in history may be misleading.

result.success is True for both SUCCESS and SKIPPED statuses. Recording skipped installations (tool already present) in history with a timestamp could mislead users into thinking an installation occurred. Consider recording only SUCCESS status.

♻️ Suggested change
         # Record in history
-        if result.success:
+        if result.status == InstallStatus.SUCCESS:
             self.record_installation(result)

580-586: Silent JSONDecodeError swallows corrupted history without warning.

If the history file is corrupted, the error is silently ignored and an empty history is used. Consider logging a warning so users know their history was lost.

🛡️ Suggested improvement
         if self.history_file.exists():
             try:
                 with open(self.history_file) as f:
                     history = json.load(f)
             except json.JSONDecodeError:
-                pass
+                logger.warning(f"Corrupted history file {self.history_file}, starting fresh")
tests/core/test_installer.py (2)

311-318: Test assertion is broader than the current implementation.

The assertion checks for apt, dnf, or yum, but the SystemInstaller implementation only outputs apt for Linux. This isn't incorrect, but the test may pass if the implementation accidentally changes to a different package manager.

Consider tightening the assertion
     def test_get_install_command_linux(self):
         """Test install command on Linux."""
         installer = SystemInstaller()
         with patch("codeframe.core.installer.get_platform", return_value="linux"):
             cmd = installer.get_install_command("git")
-        assert "apt" in cmd or "dnf" in cmd or "yum" in cmd
+        assert "apt" in cmd  # Current implementation uses apt for Linux
         assert "git" in cmd

327-334: Similar assertion broadness for Windows.

The assertion checks for choco or winget, but implementation only uses choco. Consider aligning the test with the actual implementation.

codeframe/core/environment.py (3)

407-423: First-match detection may miss polyglot projects.

Projects with multiple manifest files (e.g., pyproject.toml + package.json) will only be detected as the first-matched type. This is acceptable for v1, but consider documenting this limitation or returning a list of detected types in the future.


643-658: _detect_conflicts is a placeholder returning empty list.

The method is documented as detecting tool conflicts but currently returns an empty list. Consider either implementing real conflict detection (e.g., multiple Python versions, conflicting formatters) or adding a TODO comment explaining the intended behavior.

Would you like me to suggest implementation ideas for common tool conflicts (e.g., black vs. yapf formatter conflicts, different Python version requirements)?


659-697: Install commands partially duplicate installer.py logic.

The _get_install_command method has hardcoded install commands that overlap with PipInstaller, NpmInstaller, etc. Consider delegating to the installer module to keep commands in sync.

♻️ Suggested approach
+from codeframe.core.installer import ToolInstaller
+
+class EnvironmentValidator:
+    def __init__(self):
+        # ... existing code ...
+        self._tool_installer = ToolInstaller()
+
     def _get_install_command(self, tool_name: str, project_type: str) -> str:
-        # Hardcoded commands...
+        # Delegate to installer for supported tools
+        cmd = self._tool_installer.get_install_command(tool_name)
+        if cmd:
+            return cmd
+        
+        # Fallback for tools not in installer
+        return f"Install {tool_name} using your system package manager"

Comment thread codeframe/core/installer.py
Comment thread codeframe/core/installer.py
…aller

- NpmInstaller.install_tool: Add shutil.which() check for global installs
  when force=False, returning SKIPPED if tool already exists
- CargoInstaller.install_tool: Add pre-install check using binary name
  mapping (ripgrep->rg, fd-find->fd, clippy->cargo-clippy, cargo-edit->cargo-add)
- Skip binary check for rust-src (has no binary, it's source code)
- Add comprehensive tests for new pre-install behavior
- Remove placeholder tests/test_new_feature.py

This ensures consistent behavior across all installers: PipInstaller,
NpmInstaller, and CargoInstaller now all check if tools are already
installed before proceeding with installation.
result = installer.install_tool(tool_name, confirm=confirm, force=force)

# Record in history
if result.success:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low

When a tool is skipped (already installed), result.success is still True, causing record_installation to overwrite the original timestamp/command with None. Consider checking for InstallStatus.SUCCESS instead of result.success.

Suggested change
if result.success:
if result.status == InstallStatus.SUCCESS:

🚀 Want me to fix this? Reply ex: "fix it for me".

@frankbria frankbria merged commit e768c1d into main Jan 29, 2026
13 of 14 checks passed
@frankbria frankbria deleted the feature/environment-validation branch January 29, 2026 16:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[V2] Implement enhanced environment validation with tool detection

1 participant